use crate::utils::get_app;
use crate::window::{close_window, create_window, WindowParams};
use anyhow::Result;
use futures_util::StreamExt;
use reqwest::{Client, Response};
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use tauri::{Emitter};
use time::{format_description, OffsetDateTime};
use tokio::fs::File;
use tokio::io::{AsyncWriteExt, BufWriter};
use tokio::time::{interval, Instant};
use url::Url;
use uuid::Uuid;

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct DownloadTask {
    pub(crate) url: String,
    pub(crate) output: String,
    pub(crate) num_chunks: usize,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) enum DownloadStatus {
    Complete,
    Error(String),
    Paused(u64),
    Downloading,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct DownloadInfo {
    pub(crate) id: String,
    pub(crate) url: String,
    pub(crate) name: String,
    pub(crate) size: Option<u64>,
    pub(crate) downloaded_size: usize,
    pub(crate) status: DownloadStatus,
    pub(crate) output: String,
    pub(crate) speed: Option<f64>,
    pub(crate) progress: Option<f64>,
    pub(crate) time: String,
}

fn get_filename(url: String, response: &Response) -> String {
    // 1. 从 Content-Disposition 提取文件名
    if let Some(content_disposition) = response.headers().get(reqwest::header::CONTENT_DISPOSITION)
    {
        if let Ok(content_disposition) = content_disposition.to_str() {
            if let Some(filename) = content_disposition.split("filename=").last() {
                return filename.trim_matches('"').to_string();
            }
        }
    }

    // 2. 从 URL 提取文件名
    if let Ok(parsed_url) = Url::parse(&url) {
        if let Some(filename) = parsed_url
            .path_segments()
            .and_then(|segments| segments.last())
        {
            if !filename.is_empty() {
                return filename.to_string();
            }
        }

        // 从查询参数中尝试获取文件名（例如 ?file=example.zip）
        if let Some((_, filename)) = parsed_url
            .query_pairs()
            .find(|(key, _)| key == "file" || key == "name")
        {
            return filename.to_string();
        }
    }

    // 3. 根据 Content-Type 设置默认扩展名
    if let Some(content_type) = response.headers().get(reqwest::header::CONTENT_TYPE) {
        if let Ok(content_type) = content_type.to_str() {
            let extension = match content_type {
                "application/pdf" => "pdf",
                "image/jpeg" => "jpg",
                "image/png" => "png",
                "application/zip" => "zip",
                "text/html" => "html",
                _ => "bin", // 默认二进制文件
            };
            return format!("default.{}", extension);
        }
    }

    // 4. 最后使用默认文件名
    "default_file".to_string()
}

async fn download_chunk(
    url: &str,
    range: (u64, u64),
    chunk_index: usize,
    progress_sender: tokio::sync::mpsc::Sender<(usize, u64)>,
) -> Result<Vec<u8>> {
    let client = Client::new();

    let user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0";

    let mut response = client
        .get(url)
        .header("User-Agent", user_agent)
        .header("Range", format!("bytes={}-{}", range.0, range.1))
        .send()
        .await?;

    let mut total_downloaded = 0u64;
    let mut chunk_data = Vec::new();

    while let Some(chunk) = response.chunk().await? {
        chunk_data.extend_from_slice(&chunk);
        total_downloaded += chunk.len() as u64;

        if let Err(e) = progress_sender
            .send((chunk_index, total_downloaded as u64))
            .await
        {
            println!("download task send error {}", e);
        }
    }

    Ok(chunk_data)
}

async fn download_width_content_length(
    id: Arc<String>,
    task: DownloadTask,
    filename: String,
    total_size: u64,
) -> Result<()> {
    let app_handle = get_app()?;

    let (progress_sender, mut progress_receiver) = tokio::sync::mpsc::channel(32);

    // 分片
    let chunk_size = total_size / task.num_chunks as u64;
    let mut ranges = vec![];
    for i in 0..task.num_chunks {
        let start = i as u64 * chunk_size;
        let end = if i == task.num_chunks - 1 {
            total_size
        } else {
            (i + 1) as u64 * chunk_size - 1
        };
        ranges.push((start, end));
    }

    // 下载
    let mut handles = vec![];
    let total_chunks = task.num_chunks;
    for (i, (start, end)) in ranges.into_iter().enumerate() {
        let url = task.url.clone();
        let sender = progress_sender.clone();
        handles.push(tokio::spawn(async move {
            download_chunk(&url, (start, end), i, sender).await
        }))
    }

    // 写入文件
    let output_path = Path::new(&task.output).join(filename.clone());
    let mut file = BufWriter::new(File::create(output_path.clone()).await?);
    let mut timer = interval(Duration::from_millis(50));
    let mut start_time = Instant::now();
    let mut overall_progress = vec![0; total_chunks];

    let mut download_info = DownloadInfo {
        id: id.as_ref().clone(),
        url: task.url.to_string(),
        name: filename.clone(),
        size: Some(total_size),
        downloaded_size: 0usize,
        status: DownloadStatus::Downloading,
        output: output_path.to_str().unwrap().to_string(),
        speed: None,
        progress: None,
        time: OffsetDateTime::now_utc().to_string(),
    };

    loop {
        tokio::select! {
            // 等待进度更新
            Some((chunk_index, chunk_size)) = progress_receiver.recv() => {
                // 更新单个分块的进度
                overall_progress[chunk_index] = chunk_size as usize;
                // 计算整体进度
                let total_progress = overall_progress.iter().map(|&x| x).sum();
                download_info.downloaded_size = total_progress;
                download_info.progress = Some(total_progress as f64 / total_size as f64 * 100.0);
                // 计算当前块下载速度
                let elapsed = start_time.elapsed().as_secs_f64();
                download_info.speed = Some(chunk_size as f64 / elapsed); // byte/s

                start_time = Instant::now();
                if total_progress as u64 == total_size {
                    download_info.status = DownloadStatus::Complete;
                    download_info.time = get_now()?;
                    app_handle.emit::<DownloadInfo>("download-change",  download_info.clone())?;
                    close_window(id.as_ref().clone());
                    break;
                }
            },
            // 每过200ms通知一次前端更新进度条
            _ = timer.tick() => {
                download_info.time = get_now()?;
                app_handle.emit::<DownloadInfo>("download-change", download_info.clone())?;
            }
        }
    }
    // 写入文件
    for handle in handles {
        let data = handle.await??;
        file.write(&data).await?;
    }

    Ok(())
}

async fn download_width_stream(
    id: Arc<String>,
    task: DownloadTask,
    filename: String,
) -> Result<()> {
    let app_handle = get_app()?;
    let client = Client::new();

    let user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0";

    let response = client
        .get(&task.url)
        .header("User-Agent", user_agent)
        .send()
        .await?;

    let output_path = Path::new(&task.output).join(filename.clone());
    let mut file = BufWriter::new(File::create(output_path.clone()).await?);
    let mut body = response.bytes_stream();

    let mut timer = interval(Duration::from_millis(50));
    let mut start_time = Instant::now();

    let mut download_info = DownloadInfo {
        id: id.as_ref().clone(),
        url: task.url.to_string(),
        name: filename.clone(),
        size: None,
        downloaded_size: 0usize,
        status: DownloadStatus::Downloading,
        output: output_path.to_str().unwrap().to_string(),
        speed: None,
        progress: None,
        time: OffsetDateTime::now_utc().to_string(),
    };

    loop {
        tokio::select! {
            chunk = body.next() => {
                match chunk {
                    Some(Ok(chunk)) => {
                        download_info.downloaded_size += chunk.len();
                        download_info.speed = Some(chunk.len() as f64 / start_time.elapsed().as_secs_f64());

                        start_time = Instant::now();
                        file.write_all(&chunk).await?;
                    },
                    None => {
                        download_info.status = DownloadStatus::Complete;
                        download_info.time = get_now()?;
                        app_handle.emit::<DownloadInfo>("download-change", download_info.clone())?;

                        close_window(id.as_ref().clone());
                        break;
                    },
                    Some(Err(e)) => {
                        download_info.status = DownloadStatus::Error(format!("{:?}", e));
                        download_info.time = get_now()?;
                        app_handle.emit::<DownloadInfo>("download-change", download_info.clone())?;

                        close_window(id.as_ref().clone());
                        break;
                    }
                }
            },
            _ = timer.tick() => {
                download_info.time = get_now()?;
                app_handle.emit::<DownloadInfo>("download-change", download_info.clone())?;
            }
        };
    }

    file.flush().await?;
    app_handle.emit::<DownloadInfo>("download-change", download_info.clone())?;

    Ok(())
}

async fn download_file(id: Arc<String>, task: DownloadTask) -> Result<()> {
    let client = Client::new();

    let response = client.head(&task.url).send().await?;
    let filename = get_filename(task.url.clone(), &response);
    if let Some(content_length) = response.headers().get(reqwest::header::CONTENT_LENGTH) {
        let total_size = content_length.to_str()?.parse::<u64>()?;
        download_width_content_length(id, task, filename, total_size).await?;
    } else {
        download_width_stream(id, task, filename).await?;
    }

    Ok(())
}

#[tauri::command]
pub async fn download(url: String) -> Result<(), String> {
    println!("download: {}", url.clone());
    tokio::spawn(async move {
        let id = Arc::new(Uuid::new_v4().to_string());
        let params = WindowParams {
            id: format!("progress_window_{}", id.clone()),
            title: "".into(),
            url: format!("/progress?id={}", id.clone()),
            decorations: Some(true),
            resizable: Some(false),
            transparent: None,
            size: (400f64, 80f64),
            position: (0f64, 0f64),
        };
        if let Ok(_) = get_app() {
            if let Err(err) = create_window(params) {
                eprintln!("create window error: {}", err);
            }
        };

        let path = dirs_next::download_dir()
            .unwrap()
            .to_str()
            .unwrap()
            .to_string();
        let task = DownloadTask {
            url: url.clone(),
            output: path,
            num_chunks: 6,
        };
        if let Err(err) = download_file(id.clone(), task.clone()).await {
            eprintln!("download file error: {:?}", err);
            close_window(id.as_ref().clone());
        }
    });

    Ok(())
}

fn get_now() -> Result<String> {
    let format = format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second]")?;
    let now = OffsetDateTime::now_utc().format(&format)?;
    return Ok(now);
}
